package com.qdrtme.landemo.webapi;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import com.qdrtme.landemo.common.Utils;
import com.squareup.okhttp.Call;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.FormEncodingBuilder;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import org.json.JSONException;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateFactory;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;


/**
 * 访问网络接口抽象类，使用okHttp实现网络访问<br />
 * 使用时先定义一个继承此类的抽象类A，<br />
 * 重写getURL实现HOSTNAME的赋值，<br />
 * 重写getParams方法实现全局输入参数的赋值，<br />
 * 重写preHandle来实现返回结果的预处理<br />
 * <br />
 * 使用具体接口，继承抽象类A，再实现其他方法。<br />
 */
public abstract class WebAPI {

    //OKHttpClient单例
    private static OkHttpClient mOkHttpClient;

    private long callTag;

    //debug tag
    private static final String TAG = "WebAPI";

    //错误码
    //成功
    public static final int SUCCESS = 1;
    //网络错误
    public static final int ERROR_NETWORK = -1;
    //数据错误
    public static final int ERROR_DATA = -2;
    //服务器错误
    public static final int ERROR_SERVER = -3;
    //用户自定义错误，主动调用sendMessage方法时传此错误
    public static final int ERROR_CUSTOM = -4;

    //接口监听
    private WebAPIListener listener;

    Context context;

    /**
     * 获取Context
     *
     * @return
     */
    public Context getContext() {
        return context;
    }

    //入参
    HashMap<String, Object> params = new HashMap<>();

    //接口控制
    private boolean canceled = false;
    private boolean debug = true;
    private boolean test = false;

    private SharedPreferences mLoginSP;
    private SharedPreferences.Editor mLoginSPEditor;

    /**
     * 构造方法
     *
     * @param context
     */
    public WebAPI(Context context) {
        this.context = context;
        if (mOkHttpClient == null) {
            mOkHttpClient = new OkHttpClient();
        }
        //生成callTag;
//        callTag = new Date().getTime();
//        mLoginSP = context.getSharedPreferences(AppSP.LOGIN_SHARED_PREFERENCES, Context.MODE_PRIVATE);
//        mLoginSPEditor = mLoginSP.edit();
    }

    /**
     * 设置是否是调试模式，调试模式下，输出调试log
     *
     * @param debug 是否是调试模式
     */
    public void setDebug(boolean debug) {
        this.debug = debug;
    }

    /**
     * 设置是否是测试模式，测试模式下，不会访问网络，会直接调用解析方法analysisOutput
     *
     * @param test
     */
    public void setTest(boolean test) {
        this.test = test;
    }

    /**
     * 取消任务
     */
    public void cancel() {
        this.canceled = true;
        mOkHttpClient.cancel(callTag);
    }

    /**
     * 获取URL
     *
     * @return URL
     */
    abstract protected String getURL();

    /**
     * 通过反射获取到注解的参数
     */
    private final void injectParams() {
        params.clear();
        Class<? extends WebAPI> clazz = this.getClass();
        Field[] fields = clazz.getDeclaredFields();
        // 遍历所有成员变量
        for (Field field : fields) {
            WebAPIParams webAPIParams = field.getAnnotation(WebAPIParams.class);
            if (webAPIParams != null) {
                try {
                    field.setAccessible(true);
                    params.put(field.getName(), field.get(this));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    /**
     * 获取自定义入参，可填写全局入参
     */
    protected void getParams(HashMap<String, Object> params) {
//        params.put("default", "default");

        // 固定参数加密
//        String timeStampStr = String.valueOf(System.currentTimeMillis() / 1000);
//        params.put("sign_type", "MD5");
//        params.put("timestamp", timeStampStr);
//        List<Map<String, String>> paramsList = new ArrayList<>();
//        Map<String, String> m1 = new HashMap<>();
//        m1.put("format", "json");
//        Map<String, String> m2 = new HashMap<>();
//        m2.put("timestamp", timeStampStr);
//        Map<String, String> m3 = new HashMap<>();
//        m3.put("sign_type", "MD5");
//        paramsList.add(m1);
//        paramsList.add(m2);
//        paramsList.add(m3);
//        String codeStr = ParamsUtils.getMi(paramsList);
//        params.put("sign", codeStr);
//        params.put("sess_id", mLoginSP.getString("sess_id", ""));

    }

    /**
     * 执行Http Post请求
     *
     * @param listener 接口监听器
     */
    public final void doHttpPost(WebAPIListener listener) {
        this.listener = listener;
        doConnectInBackground("post");
    }

    /**
     * 执行Http get异步请求
     *
     * @param listener 接口监听器
     */
    public final void doHttpGet(WebAPIListener listener) {
        this.listener = listener;
        doConnectInBackground("get");
    }

    /**
     * 执行Http put异步请求
     *
     * @param listener 接口监听器
     */
    public final void doHttpPut(WebAPIListener listener) {
        this.listener = listener;
        doConnectInBackground("put");
    }

    /**
     * 执行Http delete异步请求
     *
     * @param listener 接口监听器
     */
    public final void doHttpDelete(WebAPIListener listener) {
        this.listener = listener;
        doConnectInBackground("delete");
    }

    /**
     * 开始WebAPI异步连接
     *
     * @param mode 访问模式，post或get
     * @return 连接是否成功
     */
    private void doConnectInBackground(String mode) {

        //开始
        if (listener != null) {
            listener.onStart();
        }
        //反射入参
        injectParams();
        //获取自定义入参
        getParams(params);
        //撤销取消状态
        canceled = false;
        //是测试模式，直接调用解析方法
        if (test) {
            try {
                log("测试模式");
                if (analysisOutput("")) {
                    sendMessage(SUCCESS, "");
                } else {
                    sendMessage(ERROR_DATA, "解析数据出现异常");
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
            return;
        }

        //创建okHttp访问
        String url = getURL();
        String paramsStr = "";
        log("开始访问：" + url);
//        log("参数：");
        Request request;
        Request.Builder builder = new Request.Builder();
        builder.tag(callTag);

        FormEncodingBuilder formBody = new FormEncodingBuilder();
        //遍历参数
        Iterator iter = params.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry entry = (Map.Entry) iter.next();
            String key = entry.getKey().toString();
            String val = entry.getValue().toString();
            try {
                // 参数值进行utf-8编码
//                val = URLEncoder.encode(val, "utf-8");
            } catch (Exception e) {
                e.printStackTrace();
            }
            formBody.add(key, val);
            paramsStr += key + "=" + val + (iter.hasNext() ? "&" : "");
            log("参数：" + key + "=" + val);
        }

        // TODO 插入加密处理
        long shortStamp = System.currentTimeMillis();
        String tempStr = Utils.getMi(shortStamp, params.entrySet());
        tempStr += ";" + shortStamp;
        System.out.println("## authorization- " + tempStr);

//        String s = String.valueOf(shortStamp) + "mobile" + "18562511580" + "tenantid" + "lanjing"
//                + "type" + "1" + "8w7jv4qb7eb5y"; // 15092428631   //18562511580
//        String s = "1512965874498" + "mobile" + "15092428631" + "tenantid" + "lanjing"
//                + "type" + "1" + "8w7jv4qb7eb5y";

//        System.out.println("## before md5- " + s);
//        try {
//            s = Utils.getMD5(s); // 保持原样
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//        System.out.println("## after md5- " + s);
////        s += ";" + shortStamp;
//        s += ";" + "1512965874498";
//        System.out.println("## authorization- " + s);

        if (mode.equals("post")) {
            request = new Request.Builder()
                    .url(url)    // TODO: 天气接口请求专用header参数
//                    .addHeader("Authorization", "APPCODE 25f2a93819744d60afe19b6f6daa545c")
                    // TODO 蓝睛接口Header加密
                    .addHeader("authorization", tempStr)
                    .post(formBody.build())
                    .tag(callTag)
                    .build();
        } else if (mode.equals("put")) {
            request = new Request.Builder()
                    .url(url)
                    .put(formBody.build())
                    .tag(callTag)
                    .build();
        } else if (mode.equals("delete")) {
            request = new Request.Builder()
                    .url(url)
                    .delete(formBody.build())
                    .tag(callTag)
                    .build();
        } else {
            url = url + "?" + paramsStr;
            log("full_url：" + url);
            request = new Request.Builder()
                    .url(url)
                    .addHeader("authorization", tempStr)
                    .tag(callTag)
                    .build();
        }
        //创建okHttpClient对象
        mOkHttpClient = new OkHttpClient();
        //new call
        Call call = mOkHttpClient.newCall(request);
        //请求加入调度
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {
                sendMessage(ERROR_NETWORK, "网络异常，请检查网络连接");
            }

            @Override
            public void onResponse(final Response response) throws IOException {
                String result = response.body().string();
                log("返回结果：" + result);
                if (result != null || !result.equals("")) {
                    try {
                        //预处理
                        result = preHandle(result);
                        if (result != null) {
                            if (analysisOutput(result)) {
                                sendMessage(SUCCESS, result);
                            } else {
                                sendMessage(ERROR_DATA, "数据解析异常");
                            }
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                        sendMessage(ERROR_DATA, "后台数据解析异常");
                    }
                } else {
                    sendMessage(ERROR_SERVER, "服务器未返回正确数据");
                }
            }
        });
    }

    /**
     * 预处理，不重写此方法的话，会原样返回接口的结果值result
     * 如果预处理中需要返回错误信息，则调用sendMessage输出错误信息，同时返回值为null
     *
     * @return 如果是错误状态，返回值应为null，正常则返回解析后的字符串。
     */
    protected String preHandle(String result) throws JSONException {
        return result;
    }

    /**
     * @param result 返回的字符串
     * @return 解析结果
     * @throws JSONException
     */
    abstract protected boolean analysisOutput(String result) throws JSONException;

    /**
     * 发送Handler消息
     *
     * @param what 连接状态code
     * @param obj  对象参数
     * @return 连接状态code
     */
    public void sendMessage(int what, Object obj) {
        //如果没有被取消，发送消息，否则什么也不做。
        if (!canceled) {
            handler.obtainMessage(what, obj).sendToTarget();
        }
    }

    /**
     * Handler
     */
    @SuppressWarnings("HandlerLeak")
    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //没有定义监听，返回
            if (listener == null) {
                return;
            }
            if (msg.what == SUCCESS) {
                listener.onSuccess(msg.obj.toString());
            } else {
                listener.onFail(msg.what, msg.obj.toString());
            }
            //结束接口访问
            listener.onFinish();
        }
    };

    /**
     * 输出日志
     *
     * @param text 日志内容
     */
    private void log(String text) {
        if (debug) {
            Log.d(TAG, text);
        }
    }

    /**
     * 设置密钥和安全证书，如果有https验证需要，在Application的onCreate中使用
     *
     * @param bksKey       密钥bks输入流，可传null，只实现https单向验证
     * @param password     密码，如果bls不为null，则password必须有值
     * @param certificates 证书输入流
     */
    public static void setCertificates(InputStream bksKey, String password,
                                       InputStream... certificates) {
        if (mOkHttpClient == null) {
            mOkHttpClient = new OkHttpClient();
        }
        try {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            int index = 0;
            for (InputStream certificate : certificates) {
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificateFactory
                        .generateCertificate(certificate));
                try {
                    if (certificate != null)
                        certificate.close();
                } catch (IOException e) {
                }
            }

            SSLContext sslContext = SSLContext.getInstance("TLS");
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.
                    getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);

            //如果bks不为空，则需要添加密钥
            if (bksKey != null) {
                if (password == null) {
                    throw new UnsupportedOperationException("密钥的密码为空");
                }
                KeyStore clientKeyStore = KeyStore.getInstance("BKS");
                clientKeyStore.load(bksKey, password.toCharArray());

                KeyManagerFactory keyManagerFactory = KeyManagerFactory
                        .getInstance(KeyManagerFactory.getDefaultAlgorithm());
                keyManagerFactory.init(clientKeyStore, password.toCharArray());

                sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory
                        .getTrustManagers(), new SecureRandom());
            } else {
                sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
            }
            mOkHttpClient.setSslSocketFactory(sslContext.getSocketFactory());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 构建器，用于匿名类的快速实现
     */
    public static class Builder {

        Context context;
        String url;
        HashMap<String, Object> map;

        public Builder(Context context) {
            this.context = context;
            map = new HashMap<>();
        }

        public Builder url(String url) {
            this.url = url;
            return this;
        }

        public Builder addParam(String key, Object value) {
            this.map.put(key, value);
            return this;
        }

        public WebAPI create() {
            return new WebAPI(context) {

                @Override
                protected void getParams(HashMap<String, Object> params) {
                    params.putAll(map);
                }

                @Override
                protected String getURL() {
                    return url;
                }

                @Override
                protected boolean analysisOutput(String result) throws JSONException {
                    return true;
                }
            };
        }
    }

}
